//
// Created by ht on 16-12-16.
//
#include <sstream>
#include <iostream>
#include <fstream>
#include "gtree.h"
#include "syntax.tab.hpp"

extern stringstream ss, textss;
extern ofstream irout, asmout;
extern vector<Struct> structs;
extern int param_offset;
using namespace std;

int get_stru_size(string name);

string get_tag() {
    static int index = 0;
    string tag_prefix = "t";
    return tag_prefix + to_string(++index);
}

string get_label() {
    static int index = 0;
    string label_prefix = "label";
    return label_prefix + to_string(++index);
}

bool not_used[10] = {1};

string get_reg() {
    for (int i = 0; i < 10; i++)
        if (not_used[i]) {
            not_used[i] = false;
            return "$t" + to_string(i);
        }
}

void release_reg(string reg) {
    not_used[reg[2] - '0'] = true;
}

void release_all() {
    for (int i = 0; i < 10; i++) {
        not_used[i] = true;
    }
}

string trans_exp(GTNode *exp);

void trans_StmtList(GTNode *StmtList);

static int off = 0;
int call_param_offset = 0;
int call_esp_offset = 0;

void trans_arg(GTNode *arg) {
    if (arg->child->sibiling)
        trans_arg(arg->child->sibiling->sibiling);
    trans_exp(arg->child);
    if (arg->child->type & T_ARR || arg->child->type & T_STRUCT)
        ss << "ARG &" << arg->child->tag << endl;
    else
        ss << "ARG " << arg->child->tag << endl;
}

void asm_arg(GTNode *arg) {
    if (arg->child->sibiling)
        asm_arg(arg->child->sibiling->sibiling);
    textss << "sw " << arg->child->reg << ", " << off << "($sp)\n";
    off += 4;
}

string trans_exp(GTNode *exp) {
    GTNode *child = exp->child;
    string tag;
    int offset = 0;
    string field_name;
    string stru_name;
    int elem_size = 4, dimen;
    vector<string> index;
    vector<int> arr_sizes;
    GTNode *tmp;
    string last_dimen_address, tmptag, tmptag2;
    switch (child->token) {
        case INT:
            exp->tag = "#" + to_string(child->ivalue);
            exp->reg = get_reg();
            textss << "li " << exp->reg << ", " << child->ivalue << endl;
            break;
        case FLOAT:
            exp->tag = "#" + to_string(child->fvalue);
            exp->reg = get_reg();
            textss << "l.s " << exp->reg << ", " << child->ivalue << endl;
            break;
        case LP:
            trans_exp(child->sibiling);
            exp->tag = child->sibiling->tag;
            exp->reg = child->sibiling->reg;
            break;
        case MINUS:
            trans_exp(child->sibiling);
            exp->tag = get_tag();
            ss << exp->tag << " := #0 - " << child->sibiling->tag << endl;
            exp->reg = get_reg();
            //textss<<exp->reg
            break;
        case ID:
            if (!child->sibiling) {
                exp->tag = child->name;
                if (!exp->sibiling || exp->sibiling->token != ASSIGNOP) {
                    exp->reg = get_reg();
                    textss << "lw " << exp->reg << ", " << exp->offset << "($sp)" << endl;
                }
            } else {
                if (child->name == "read") {
                    exp->tag = get_tag();
                    ss << "READ " << exp->tag << endl;
                    exp->reg = get_reg();
                    textss << "addi $sp, $sp, -4\nsw $ra, 0($sp)\njal read\nlw $ra, 0($sp)\naddi $sp, $sp, 4\nmove "
                           << exp->reg << ", $v0\n";
                } else {
                    GTNode *args = child->sibiling->sibiling;
                    if (child->name == "write") {
                        trans_exp(args->child);
                        ss << "WRITE " << args->child->tag << endl;
                        textss << "move $a0, " << args->child->reg << endl
                               << "addi $sp, $sp, -4\nsw $ra, 0($sp)\njal write\nlw $ra, 0($sp)\naddi $sp, $sp, 4\n";
                    } else {
                        call_param_offset = exp->dimen;
                        call_esp_offset = exp->offset;
                        if (args->sibiling) //如果有参数
                            trans_arg(args);
                        textss << "addi $sp, $sp, -" << 4 + call_param_offset + call_esp_offset << "\nsw $ra, "
                               << call_param_offset + call_esp_offset << "($sp)\n";
                        if (args->sibiling) {
                            asm_arg(args);
                            off = 0;
                        }
                        exp->tag = get_tag();
                        exp->reg=get_reg();
                        ss << exp->tag << " := CALL " << child->name << endl;
                        textss << "jal " << child->name << "\nlw $ra, " << call_param_offset + call_esp_offset
                               << "($sp)\naddi $sp, $sp, " << 4 + call_param_offset + call_esp_offset << "\nmove "
                               << exp->reg << ", $v0\n";
                    }
                }
            }
            break;
        case NOT:
            break;
        default:
            switch (child->sibiling->token) {
                case ASSIGNOP:
                    trans_exp(child->sibiling->sibiling);
                    trans_exp(child);
                    ss << child->tag << " := " << child->sibiling->sibiling->tag << endl;
                    textss << "sw " << child->sibiling->sibiling->reg << ", " << child->offset << "($sp)\n";
                    release_reg(child->sibiling->sibiling->reg);
                    break;
                case AND:
                    break;
                case OR:
                    break;
                case RELOP:
                    trans_exp(child->sibiling->sibiling);
                    trans_exp(child);
                    if (child->sibiling->name == "==") {
                        textss << "beq " << child->reg << ", " << child->sibiling->sibiling->reg << ", ";
                    } else if (child->sibiling->name == "!=") {
                        textss << "bne " << child->reg << ", " << child->sibiling->sibiling->reg << ", ";
                    } else if (child->sibiling->name == ">") {
                        textss << "bgt " << child->reg << ", " << child->sibiling->sibiling->reg << ", ";
                    } else if (child->sibiling->name == "<") {
                        textss << "blt " << child->reg << ", " << child->sibiling->sibiling->reg << ", ";
                    } else if (child->sibiling->name == ">=") {
                        textss << "bge " << child->reg << ", " << child->sibiling->sibiling->reg << ", ";
                    } else if (child->sibiling->name == "<=") {
                        textss << "ble " << child->reg << ", " << child->sibiling->sibiling->reg << ", ";
                    } else {
                        textss << "Error RELOP\n";
                    }
                    return child->tag + " " + child->sibiling->name + " " + child->sibiling->sibiling->tag;
                    break;
                case PLUS:
                    trans_exp(child->sibiling->sibiling);
                    trans_exp(child);
                    exp->tag = get_tag();
                    ss << exp->tag << " := " << child->tag << " + " << child->sibiling->sibiling->tag << endl;
                    exp->reg = get_reg();
                    textss << "add " << exp->reg << ", " << child->reg << ", " << child->sibiling->sibiling->reg
                           << endl;
                    release_reg(child->reg);
                    release_reg(child->sibiling->sibiling->reg);
                    break;
                case MINUS:
                    trans_exp(child->sibiling->sibiling);
                    trans_exp(child);
                    exp->tag = get_tag();
                    ss << exp->tag << " := " << child->tag << " - " << child->sibiling->sibiling->tag << endl;
                    exp->reg = get_reg();
                    textss << "sub " << exp->reg << ", " << child->reg << ", " << child->sibiling->sibiling->reg
                           << endl;
                    release_reg(child->reg);
                    release_reg(child->sibiling->sibiling->reg);
                    break;
                case STAR:
                    trans_exp(child->sibiling->sibiling);
                    trans_exp(child);
                    exp->tag = get_tag();
                    ss << exp->tag << " := " << child->tag << " * " << child->sibiling->sibiling->tag << endl;
                    exp->reg = get_reg();
                    textss << "mul " << exp->reg << ", " << child->reg << ", " << child->sibiling->sibiling->reg
                           << endl;
                    release_reg(child->reg);
                    release_reg(child->sibiling->sibiling->reg);
                    break;
                case DIV:
                    trans_exp(child->sibiling->sibiling);
                    trans_exp(child);
                    exp->tag = get_tag();
                    ss << exp->tag << " := " << child->tag << " / " << child->sibiling->sibiling->tag << endl;
                    exp->reg = get_reg();
                    textss << "div " << exp->reg << ", " << child->reg << ", " << child->sibiling->sibiling->reg
                           << endl;
                    release_reg(child->reg);
                    release_reg(child->sibiling->sibiling->reg);
                    break;
                case LB:
                    if (child->type & T_STRUCT) {
                        for (int s = 0; s < structs.size(); s++) {
                            if (structs[s].name == child->struct_name) {
                                elem_size = structs[s].size;
                                break;
                            }
                        }
                    }
                    tmp = child;
                    while (1) {
                        trans_exp(tmp->sibiling->sibiling);
                        index.insert(index.begin(), tmp->sibiling->sibiling->tag);
                        if (!(tmp->child->sibiling))
                            break;
                        tmp = tmp->child;
                    }
                    trans_exp(tmp);
                    dimen = tmp->dimen;
                    arr_sizes = tmp->sizes;
                    last_dimen_address = "#0";
                    for (int d = 0; d < dimen; d++) {
                        int off = 1;
                        for (int x = 0; x < dimen - d - 1; x++) {
                            off *= arr_sizes[x];
                        }
                        string tmp_tag1 = get_tag(), tmp_tag2 = get_tag();
                        ss << tmp_tag1 << " := " << index[d] << " * #" << off << endl;
                        ss << tmp_tag2 << " := " << last_dimen_address << " + " << tmp_tag1 << endl;
                        last_dimen_address = tmp_tag2;
                    }
                    tmptag = get_tag();
                    tmptag2 = get_tag();
                    ss << tmptag << " := " << last_dimen_address << " * #" << elem_size << endl;
                    ss << tmptag2 << " := " << tmptag << " + " << tmp->tag << endl;
                    exp->tag = "*" + tmptag2;
                    break;
                case DOT:
                    field_name = child->sibiling->sibiling->name;
                    stru_name = child->struct_name;
                    trans_exp(child);
                    for (int i = 0; i < structs.size(); i++) {
                        if (structs[i].name == stru_name) {
                            for (int j = 0; j < structs[i].fields.size(); j++) {
                                StructField f = structs[i].fields[j];
                                if (f.name == field_name) {
                                    string field_tag = get_tag();
                                    ss << field_tag << " := " << child->tag << " + #" << offset << endl;
                                    exp->tag = "*" + field_tag;
                                } else {
                                    if (f.type & T_ARR) {
                                        int arr_size = 1;
                                        for (int m = 0; m < f.dimen; m++)
                                            arr_size *= f.sizes[m];
                                        if (f.type & T_STRUCT) {
                                            offset += arr_size * get_stru_size(f.struct_name);
                                        } else {
                                            offset += arr_size * 4;
                                        }
                                    } else if (f.type == T_STRUCT) {
                                        offset += get_stru_size(f.struct_name);
                                    } else {
                                        offset += 4;
                                    }
                                }
                            }
                        }
                    }
                    break;
                default:
                    cout << "Error exp" << endl;
                    break;
            }
            break;
    }
    return "";
}

void trans_stmt(GTNode *stmt) {
    GTNode *stmt_child = stmt->child;
    string child_name = stmt_child->name;
    if (child_name == "Exp") {
        trans_exp(stmt_child);
    } else if (child_name == "if") {
        if (!stmt_child->sibiling->sibiling->sibiling->sibiling->sibiling) {
            string label1 = get_label();
            string label2 = get_label();
            ss << "IF " << trans_exp(stmt_child->sibiling->sibiling);
            ss << " GOTO " << label1 << endl;
            textss<<label1<<":\n";
            ss << "GOTO " << label2 << endl;
            textss<<"j "<<label2<<endl;
            ss << "LABEL " << label1 << " :" << endl;
            textss<<label1<<":\n";
            trans_stmt(stmt_child->sibiling->sibiling->sibiling->sibiling);
            ss << "LABEL " << label2 << " :" << endl;
            textss<<label2<<":\n";
        } else {
            string label1 = get_label();
            string label2 = get_label();
            string label3 = get_label();
            ss << "IF " << trans_exp(stmt_child->sibiling->sibiling) << " GOTO " << label1 << endl;
            textss<<label1<<endl;
            ss << "GOTO " << label2 << endl;
            textss<<"j "<<label2<<endl;
            ss << "LABEL " << label1 << " :" << endl;
            textss<<label1<<":\n";
            trans_stmt(stmt_child->sibiling->sibiling->sibiling->sibiling);
            ss << "GOTO " << label3 << endl;
            textss<<"j "<<label3<<endl;
            ss << "LABEL " << label2 << " :" << endl;
            textss<<label2<<":\n";
            trans_stmt(stmt_child->sibiling->sibiling->sibiling->sibiling->sibiling->sibiling);
            ss << "LABEL " << label3 << " :" << endl;
            textss<<label3<<":\n";
        }

    } else if (child_name == "CompSt") {
        trans_StmtList(stmt_child->child->sibiling->sibiling);
    } else if (child_name == "return") {
        trans_exp(stmt_child->sibiling);
        ss << "RETURN " << stmt_child->sibiling->tag << endl;
        textss << "move $v0, " << stmt_child->sibiling->reg << endl;
        textss << "jr $ra\n";
        release_reg(stmt_child->sibiling->reg);
    } else if (child_name == "while") {
        string label1 = get_label(), label2 = get_label(), label3 = get_label();
        ss << "LABEL " << label1 << " :" << endl;
        textss << label1 << ":\n";
        ss << "IF " << trans_exp(stmt_child->sibiling->sibiling) << " GOTO " << label2 << endl;
        textss << label2 <<"\n";
        ss << "GOTO " << label3 << endl;
        textss << "j " << label3 << endl;

        ss << "LABEL " << label2 << " :" << endl;
        textss << label2 << ":\n";

        trans_stmt(stmt_child->sibiling->sibiling->sibiling->sibiling);
        ss << "GOTO " << label1 << endl;
        ss << "LABEL " << label3 << " :" << endl;

        textss << "j " << label1 << endl;
        textss << label3 << ":\n";
    } else
        cout << "Error Stmt" << endl;
}

void trans_StmtList(GTNode *StmtList) {
    GTNode *stmt = StmtList->child;
    while (stmt) {
        trans_stmt(stmt);
        stmt = stmt->sibiling->child;
    }
}

